package orchestration.utils;

import java.io.Serializable;
import java.lang.reflect.Array;
import java.util.List;
import java.util.Map;

import orchestration.businessprocess.execution.Property;
import orchestration.persistence.exceptions.UnsupportedTypeException;
import android.content.Intent;
import android.net.Uri;
import android.os.Parcel;
import android.os.Parcelable;

public class PropertyUtils {

	@SuppressWarnings("unchecked")
	public static void writePropertyValueToParcel(Parcel parcel, Property property){
		
		Object value= property.getValue();
		Class<?> type= property.getType();
		if(value==null)
			parcel.writeValue(value);
		if(type.isArray()){ 
			Class<?> componentType = type.getComponentType();
			//TODO support "normal" types
			if(componentType.equals(int.class) || componentType.equals(Integer.class))
				parcel.writeIntArray((int[])value);
			else if(componentType.equals(double.class) || componentType.equals(Double.class))
				parcel.writeDoubleArray((double[])value);
			else if(componentType.equals(float.class) || componentType.equals(Float.class))
				parcel.writeFloatArray((float[])value);
			else if(componentType.equals(long.class) || componentType.equals(Long.class))
				parcel.writeLongArray((long[])value);
			else if(componentType.equals(boolean.class) || componentType.equals(Boolean.class))
				parcel.writeBooleanArray((boolean[])value);
			else if(componentType.equals(char.class) || componentType.equals(Character.class))
				parcel.writeCharArray((char[])value);
			else if(componentType.equals(String.class))
				parcel.writeStringArray((String[])value);
			else if(componentType.equals(Uri.class))
				parcel.writeTypedArray((Uri[])value, 0);//FIXME: writeTypedArray needs a T[], find some way to extend Uri case to all parcelables!
			else if(componentType.equals(Parcelable.class))
				parcel.writeTypedArray((Parcelable[])value, 0);
		}else if(type.equals(Integer.class) || property.equals(int.class))
			parcel.writeInt((Integer)value);
        else if (type.equals(String.class)) 
        	 parcel.writeString((String)value);
         else if (type.equals(Long.class) || property.equals(long.class)) 
            parcel.writeLong((Long)value);
         else if (type.equals(Float.class) || property.equals(float.class)) 
            parcel.writeFloat((Float) value);
         else if (type.equals(Double.class) || property.equals(double.class)) 
            parcel.writeDouble((Double) value);
         else if(Map.class.isAssignableFrom(type))
        	 parcel.writeMap(Map.class.cast(value));
         else if(List.class.isAssignableFrom(type)){
        	 if(property.getComponentType()==null)
        		 parcel.writeList((List)value);
        	 else
        		 parcel.writeTypedList((List)value);
         }else if(Parcelable.class.isAssignableFrom(type)){
        	 parcel.writeInt(1);
        	 ((Parcelable)value).writeToParcel(parcel, 0);
         }
	}
	@SuppressWarnings("unchecked")
	public static void readPropertyValueFromParcel(Parcel parcel, Property property){
		//TODO add support for array and List
		Object value=null;
		Class<?> type= property.getType();
		Class<?> componentType;
		if(type.isArray()){
			componentType=type.getComponentType();
			if(componentType.equals(int.class) || componentType.equals(Integer.class))
				value=parcel.createIntArray();
			else if(componentType.equals(double.class) || componentType.equals(Double.class))
				value=parcel.createDoubleArray();
			else if(componentType.equals(float.class) || componentType.equals(Float.class))
				value=parcel.createFloatArray();
			else if(componentType.equals(long.class) || componentType.equals(Long.class))
				value=parcel.createLongArray();
			else if(componentType.equals(boolean.class) || componentType.equals(Boolean.class))
				value=parcel.createBooleanArray();
			else if(componentType.equals(char.class) || componentType.equals(Character.class))
				value=parcel.createCharArray();
			else if(componentType.equals(String.class))
				value=parcel.createStringArray();
			else if(Parcelable.class.isAssignableFrom(componentType)){
				try {
					value=parcel.createTypedArray(((Parcelable.Creator)componentType.getField("CREATOR").get(null)));
				} catch (IllegalArgumentException e) {
					e.printStackTrace();
				} catch (SecurityException e) {
					e.printStackTrace();
				} catch (IllegalAccessException e) {
					e.printStackTrace();
				} catch (NoSuchFieldException e) {
					e.printStackTrace();
				}
			}
		}else if(type.equals(Integer.class))
			value=parcel.readInt();
		else if(type.equals(Double.class))
			value=parcel.readDouble();
		else if(type.equals(Long.class))
			value=parcel.readLong();
		else if(type.equals(Float.class))
			value=parcel.readFloat();
		else if(type.equals(String.class))
			value=parcel.readString();
		else if(List.class.isAssignableFrom(type)){
			componentType= property.getComponentType();
				if(componentType==null){
					value=parcel.readArrayList(property.getClass().getClassLoader());
				}else if(componentType.equals(String.class)){
					value=parcel.createStringArrayList();
				}else if(Parcelable.class.isAssignableFrom(componentType)){
					try {
						value=parcel.createTypedArrayList((Parcelable.Creator)componentType.getField("CREATOR").get(null));
					} catch (IllegalArgumentException e) {
						e.printStackTrace();
					} catch (SecurityException e) {
						e.printStackTrace();
					} catch (IllegalAccessException e) {
						e.printStackTrace();
					} catch (NoSuchFieldException e) {
						e.printStackTrace();
					}
				}
		}
		else{
			if ((0!=parcel.readInt() && Parcelable.class.isAssignableFrom(type))) {
				try {
					value = ((Parcelable.Creator)type.getField("CREATOR").get(null)).createFromParcel(parcel);
				} catch (IllegalArgumentException e) {
					e.printStackTrace();
				} catch (SecurityException e) {
					e.printStackTrace();
				} catch (IllegalAccessException e) {
					e.printStackTrace();
				} catch (NoSuchFieldException e) {
					e.printStackTrace();
				}
			}
		}
		property.setValue(value);
	}
	public static void writePropertyValueToIntent(Intent intent,Property property, String mappedName) throws UnsupportedTypeException{
		Object value= property.getValue();
		if(mappedName==null)
			mappedName=property.getName();
		try{
			intent.putExtra(mappedName, (Serializable)value);
			return;
		}catch(ClassCastException e){}
		try{
			intent.putExtra(mappedName, (Parcelable)value);
			return;
		}catch(ClassCastException e){}
		throw new UnsupportedTypeException(property.getType());
	}
	
	public static void readPropertyValueFromIntent(Intent intent,Property property, String mappedName) throws UnsupportedTypeException{
		Class<?> type=property.getType();
		Class<?> componentType = type.getComponentType();
		Object propValue=null;
		if(mappedName==null)
			mappedName=property.getName();	
		//special case: array of parcelables
		if(type.isArray() && Parcelable.class.isAssignableFrom(componentType)){
			Parcelable[] parcArray= intent.getParcelableArrayExtra(mappedName);
			propValue=Array.newInstance(type.getComponentType(), parcArray.length);
			for(int i=0;i<parcArray.length;i++){
				Array.set(propValue, i, componentType.cast(parcArray[i]));
			}
		}else if(Parcelable.class.isAssignableFrom(type)){
			propValue=intent.getParcelableExtra(mappedName);
		}else if(Serializable.class.isAssignableFrom(type)) {
			propValue=intent.getSerializableExtra(mappedName);
		}else
			throw new UnsupportedTypeException(type);
		property.setValue(propValue);
	}
	
	public static boolean typeIsSupported(Class<?> type){
		if(type.isArray())
			type=type.getComponentType();
		return (Serializable.class.isAssignableFrom(type) || Parcelable.class.isAssignableFrom(type));
	}
	public static String getStringValue(Property property) {
		Object value = property.getValue();
		return value==null ? null : value.toString();
	}
	public static void setValueFromString(String value, Property property,int position) throws UnsupportedTypeException {
		if(property.getType().isArray())
			Array.set(property.getValue(), position, string2obj(value,property.getType().getComponentType()));
		else
			property.setValue(string2obj(value, property.getType()));
	}
	public static Object string2obj(String value,Class<?> aClass) throws UnsupportedTypeException{
		if(value==null)
			return null;
		if(aClass.equals(int.class) || aClass.equals(Integer.class))
			return Integer.parseInt(value);
		else if(aClass.equals(double.class) || aClass.equals(Double.class))
			return Double.parseDouble(value);
		else if(aClass.equals(boolean.class) || aClass.equals(Boolean.class))
			return Boolean.parseBoolean(value);
		else if(aClass.equals(Uri.class))
			return Uri.parse(value);
		else if(aClass.equals(String.class))
			return value;
		else throw new UnsupportedTypeException(aClass.getCanonicalName());
	}
}
